#include "Board.h"
#include <stdlib.h>
#include <memory.h>
#include <stdio.h>
#include <math.h>

Board::Board(int size) : m_nScore(0), m_nSize(size), m_pAvailableCells(0)
{
	m_pValues = (int*) malloc(m_nSize * m_nSize * sizeof(int));
	memset(m_pValues, 0, m_nSize * m_nSize * sizeof(int));

	m_pMerged = (int*) malloc(m_nSize * m_nSize * sizeof(int));
	memset(m_pMerged, 0, m_nSize * m_nSize * sizeof(int));
}


Board::Board(const Board &b) : m_nScore(0), m_nSize(b.m_nSize), m_pAvailableCells(0)
{
	m_pValues = (int*) malloc(m_nSize * m_nSize * sizeof(int));
	memcpy(m_pValues, b.m_pValues, m_nSize * m_nSize * sizeof(int));

	m_pMerged = (int*) malloc(m_nSize * m_nSize * sizeof(int));
	memset(m_pMerged, 0, m_nSize * m_nSize * sizeof(int));
}


Board::~Board(void)
{
	free(m_pValues);
	free(m_pMerged);
	free(m_pAvailableCells);
}


Board & Board::operator=(const Board &b)
{
	free(m_pValues);
	free(m_pMerged);

	m_pValues = (int*) malloc(m_nSize * m_nSize * sizeof(int));
	memcpy(m_pValues, b.m_pValues, m_nSize * m_nSize * sizeof(int));

	m_pMerged = (int*) malloc(m_nSize * m_nSize * sizeof(int));
	memset(m_pMerged, 0, m_nSize * m_nSize * sizeof(int));

	return *this;
}


void Board::ResetMerged()
{
	memset(m_pMerged, 0, m_nSize * m_nSize * sizeof(int));
}


const bool Board::MoveLeft()
{
	bool bMoved = false;

	for (int i=0; i<m_nSize; i++)
	{
		for (int j=0; j<m_nSize; j++)
		{
			Cell c = {i, j};
			if (0 == Value(c))
				continue;
			
			Cell candidate = c;
			Cell next = c.Left();
			while (IsValidCell(next) && 0 == Value(next))
			{
				candidate = next;
				next = next.Left();
			}

			if (IsValidCell(next) && 0 == Merged(next) && Value(next) == Value(c))
			{
				Merged(next) = 1;
				Value(next) = Value(next) * 2;
				Value(c) = 0;

				m_nScore += Value(next);
				bMoved = true;
			}
			else if (!c.IsSame(candidate))
			{
				// move tile 'c' to candidate
				Value(candidate) = Value(c);
				Value(c) = 0;

				bMoved = true;
			}
		}
	}

	ResetMerged();
	return bMoved;
}


const bool Board::MoveRight()
{
	bool bMoved = false;

	for (int i=0; i<m_nSize; i++)
	{
		for (int j=m_nSize-1; j>=0; j--)
		{
			Cell c = {i, j};
			if (0 == Value(c))
				continue;
			
			Cell candidate = c;
			Cell next = c.Right();
			while (IsValidCell(next) && 0 == Value(next))
			{
				candidate = next;
				next = next.Right();
			}

			if (IsValidCell(next) && 0 == Merged(next) && Value(next) == Value(c))
			{
				Merged(next) = 1;
				Value(next) = Value(next) * 2;
				Value(c) = 0;

				m_nScore += Value(next);
				bMoved = true;
			}
			else if (!c.IsSame(candidate))
			{
				// move tile 'c' to candidate
				Value(candidate) = Value(c);
				Value(c) = 0;

				bMoved = true;
			}
		}
	}

	ResetMerged();
	return bMoved;
}


const bool Board::MoveUp()
{
	bool bMoved = false;

	for (int i=0; i<m_nSize; i++)
	{
		for (int j=0; j<m_nSize; j++)
		{
			Cell c = {i, j};
			if (0 == Value(c))
				continue;
			
			Cell candidate = c;
			Cell next = c.Up();
			while (IsValidCell(next) && 0 == Value(next))
			{
				candidate = next;
				next = next.Up();
			}

			if (IsValidCell(next) && 0 == Merged(next) && Value(next) == Value(c))
			{
				Merged(next) = 1;
				Value(next) = Value(next) * 2;
				Value(c) = 0;

				m_nScore += Value(next);
				bMoved = true;
			}
			else if (!c.IsSame(candidate))
			{
				// move tile 'c' to candidate
				Value(candidate) = Value(c);
				Value(c) = 0;

				bMoved = true;
			}
		}
	}

	ResetMerged();
	return bMoved;
}


const bool Board::MoveDown()
{
	bool bMoved = false;

	for (int i=m_nSize-1; i>=0; i--)
	{
		for (int j=0; j<m_nSize; j++)
		{
			Cell c = {i, j};
			if (0 == Value(c))
				continue;
			
			Cell candidate = c;
			Cell next = c.Down();
			while (IsValidCell(next) && 0 == Value(next))
			{
				candidate = next;
				next = next.Down();
			}

			if (IsValidCell(next) && 0 == Merged(next) && Value(next) == Value(c))
			{
				Merged(next) = 1;
				Value(next) = Value(next) * 2;
				Value(c) = 0;

				m_nScore += Value(next);
				bMoved = true;
			}
			else if (!c.IsSame(candidate))
			{
				// move tile 'c' to candidate
				Value(candidate) = Value(c);
				Value(c) = 0;

				bMoved = true;
			}
		}
	}

	ResetMerged();
	return bMoved;
}


const bool Board::CanMove()
{
	Board b = *this;
	if (b.MoveLeft())
		return true;

	b = *this;
	if (b.MoveDown())
		return true;

	return false;
}


const int Board::AvailableCellsCount()
{
	int count = 0;
	for (int i = 0; i < m_nSize*m_nSize; i++)
	{
		if (0 == m_pValues[i])
			count++;
	}

	return count;
}


const Cell * Board::AvailableCells()
{
	int count = 0;
	if (m_pAvailableCells)
		free(m_pAvailableCells);
	m_pAvailableCells = (Cell*)malloc(sizeof(Cell) * AvailableCellsCount());
	
	for (int i = 0; i < m_nSize; i++)
	{
		for (int j = 0; j < m_nSize; j++)
		{
			if (0 == Value(i, j))
			{
				m_pAvailableCells[count].row = i;
				m_pAvailableCells[count].col = j;
				count++;
			}
		}
	}

	return m_pAvailableCells;
}


void Board::Print()
{
	for (int i = 0; i < m_nSize; i++)
	{
		for (int j = 0; j < m_nSize; j++)
		{
			printf("\t%d", Value(i, j));
		}
		printf("\n");
	}
	printf("\n");
}


void Board::Print(const Cell &recent)
{
	for (int i = 0; i < m_nSize; i++)
	{
		for (int j = 0; j < m_nSize; j++)
		{
			if (recent.row == i && recent.col == j)
				printf("\t[%d]", Value(i, j));
			else
				printf("\t%d", Value(i, j));
		}
		printf("\n");
	}
	printf("\n");
}


const float Board::Evaluate()
{
	float smoothWeight = 0.1f;
	float mono2Weight = 1.0f;
	float emptyWeight = 2.7f;
	float maxWeight = 1.0f;

	//float mono2 = mono2Weight * Monocity2();
	//float empty = emptyWeight * log(AvailableCellsCount() * 1.0f);
	//float maxVal = maxWeight * MaxValue();

	return //m_nScore +
		smoothWeight +
		Monocity2() * mono2Weight +
		log(AvailableCellsCount() * 1.0f) * emptyWeight +
		MaxValue() * maxWeight;
}


const int Board::MaxValue()
{
	int maxValue = 0;
	for (int i = 0; i < m_nSize*m_nSize; i++)
	{
		if (maxValue < m_pValues[i])
			maxValue = m_pValues[i];
	}

	return maxValue;
}


int log2(int n)
{
	int log2_ = 0;
	while (n > 1)
	{
		log2_++;
		n >>= 1;
	}

	return log2_;
}

const int Board::Monocity2()
{
	int totals[] = {0, 0, 0, 0};

	for (int x=0; x<m_nSize; x++)
	{
		int curr = 0, next = 1;
		while (next < m_nSize)
		{
			while (next < m_nSize && Value(x, next) == 0)
				next++;
			if (next >= m_nSize) next--;

			int currVal = log2(Value(x, curr));
			int nextVal = log2(Value(x, next));

			if (currVal > nextVal)
				totals[0] += nextVal - currVal;
			else if (currVal < nextVal)
				totals[1] += currVal - nextVal;

			curr = next++;
		}
	}
	if (totals[0] < totals[1])
		totals[0] = totals[1];

	for (int y=0; y<m_nSize; y++)
	{
		int curr = 0, next = 1;
		while (next < m_nSize)
		{
			while (next < m_nSize && Value(next, y) == 0)
				next++;
			if (next >= m_nSize) next--;

			int currVal = log2(Value(curr, y));
			int nextVal = log2(Value(next, y));

			if (currVal > nextVal)
				totals[2] += nextVal - currVal;
			else if (currVal < nextVal)
				totals[3] += currVal - nextVal;

			curr = next++;
		}
	}
	if (totals[2] < totals[3])
		totals[2] = totals[3];

	return totals[0] + totals[2];
}
